/*

MiscVolumeMeter
Version 1.2
Copyright (c) 1995 by Sean Luke
Donated to the MiscKit

Permission to use, copy, modify, and distribute this material 
for any purpose and without fee, under the restrictions as noted 
in the MiscKit copyright notice, is hereby granted, provided that
the MiscKit copyright notice and this permission notice 
appear in all source copies, and that the author's name shall not
be used in advertising or publicity pertaining to this 
material without the specific, prior written permission 
of the author.  SEAN O. LUKE  MAKES NO REPRESENTATIONS ABOUT THE
ACCURACY OR SUITABILITY OF THIS MATERIAL FOR ANY PURPOSE.  
IT IS PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.

*/

#import "MiscVolumeMeter.h"
#import <stdio.h>

@implementation MiscVolumeMeter

// slave method to the below timed entry function

- _update
	{
	if ([delegate respondsTo:@selector(meterWillUpdateOnOwn:)])
		[delegate meterWillUpdateOnOwn:self];
	return [self display];
	}


// timed entry procedure for periodically updating

DPSTimedEntryProc VOLUMEMETER_update_meter
	(DPSTimedEntry teNum,double now, void* the_meter)
	{
	MiscVolumeMeter* temp_meter=(MiscVolumeMeter*) the_meter;
	//printf ("Display!\n");
	[temp_meter _update];
	return (void*) NULL;
	}


// convenience functions

BOOL VOLUMEMETER_draw_wide(const NXRect* aRect)
	{
	return (BOOL) (aRect->size.width>=aRect->size.height);
	}

BOOL VOLUMEMETER_can_draw(const NXRect* aRect)
	{
	return (BOOL) (aRect->size.width>VOLUMEMETER_VALUE_INSET*2
		&&aRect->size.height>VOLUMEMETER_VALUE_INSET*2);
	}



// methods

- initFrame:(const NXRect*) frameRect
	{
	int x;
	
	[super initFrame:frameRect];
	
	delegate=NULL;
	input=NO;
	running=NO;
	bezeled=YES;
	peak_bubble_displayed=YES;
	stereo=YES;
	background_gray=NX_DKGRAY;
	value_gray=NX_LTGRAY;
	bubble_gray=NX_WHITE;
	refresh=VOLUMEMETER_TIMED_ENTRY_SPEED;
	refreshes_per_new_peak_bubble=VOLUMEMETER_STD_REFRESHES;
	refresh_tally=0;
	for (x=0;x<VOLUMEMETER_MAX_REFRESHES;x++) 
		{refreshes_left[x]=0.0;refreshes_right[x]=0.0;}
	current_max_refresh_left=0;
	current_max_refresh_right=0;
	
	if (input_device==NULL) input_device= [[NXSoundIn alloc] init];
	if (output_device==NULL) output_device=[[NXSoundOut alloc] init];
	[input_device setDetectPeaks:YES];
	[output_device setDetectPeaks:YES];
	teNum=0;
	
	return self;
	}
	
	

- drawSelf:(const NXRect*) rects:(int) rectCount
	{
	NXRect drawRectLeft,drawRectRight;
	NXRect backgroundRect=bounds;
	NXRect valueRect=bounds;
	float left,right;
	int just_erase=0;
	
	//if (![window isVisible]) return NULL;		// no window to draw in.
	// the above has been turned off because when loading Resound,
	// the sound meter wouldn't display until a sound was being played
	// or recorded!
	
	if ([delegate respondsTo:@selector(meterWillUpdate:)])
		[delegate meterWillUpdate:self];
	
	// first to check to see if the sound lock is current
	//printf ("Meter\n");
	if (sound!=NULL)
		{
		int status=NX_SoundStopped;
		id actual_sound=NULL;			// quiets compiler complaints
		
		if ([sound isKindOf:[Sound class]]) 
			{actual_sound=sound;}
		else if ([sound isKindOf:[SoundView class]])
			{actual_sound=[sound soundBeingProcessed];}
		status=[actual_sound status];
		if (status==NX_SoundStopped||
			status==NX_SoundInitialized||
			status==NX_SoundFreed) 
		{just_erase=1;}
			
		// Then modify the meter to match the sound	
		
		else if (status==NX_SoundRecordingPaused||
				 status==NX_SoundRecording||
				 status==NX_SoundRecordingPending)
		{[self setToInput];}
		
		else if (status==NX_SoundPlayingPaused||
				 status==NX_SoundPlaying||
				 status==NX_SoundPlayingPending)
		{[self setToOutput];}
		
		if ([actual_sound channelCount]>1)
			{[self setStereo];}
		else {[self setMono];}
		}
	
	// then check for bezeled stuff
	
	
	if (bezeled)
		{
		backgroundRect.origin.x		+=VOLUMEMETER_BACKGROUND_INSET; 
		backgroundRect.size.width	-=VOLUMEMETER_BACKGROUND_INSET*2;
		backgroundRect.origin.y		+=VOLUMEMETER_BACKGROUND_INSET; 
		backgroundRect.size.height	-=VOLUMEMETER_BACKGROUND_INSET*2;
		
		valueRect.origin.x		+=VOLUMEMETER_VALUE_INSET;
		valueRect.size.width 	-=VOLUMEMETER_VALUE_INSET*2;
		valueRect.origin.y		+=VOLUMEMETER_VALUE_INSET;
		valueRect.size.height	-=VOLUMEMETER_VALUE_INSET*2;
		}
	else
		{
		valueRect.origin.x		+=
				VOLUMEMETER_VALUE_INSET-VOLUMEMETER_BACKGROUND_INSET;
		valueRect.size.width 	-=
				VOLUMEMETER_VALUE_INSET*2-VOLUMEMETER_BACKGROUND_INSET*2;
		valueRect.origin.y		+=
				VOLUMEMETER_VALUE_INSET-VOLUMEMETER_BACKGROUND_INSET;
		valueRect.size.height	-=
				VOLUMEMETER_VALUE_INSET*2-VOLUMEMETER_BACKGROUND_INSET*2;
		}
	
	if (!VOLUMEMETER_can_draw(&bounds)) return self;	// can't draw
	
	if (bezeled) NXDrawGrayBezel(&bounds,NULL);
	PSsetgray(background_gray);
	NXRectFill(&backgroundRect);
	
	if (just_erase) return self;
	
	// compute for drawing
	
	if (running)
		{
		left=0;right=0;
		
		if (input&&input_device!=NULL) 
			[input_device getPeakLeft:&left right:&right];
		
		if ((!input)&&output_device!=NULL)
			[output_device getPeakLeft:&left right:&right];
			
		if (left>1) left=1; if (right>1) right=1;
			// occasionally a NeXTSTEP bug returns values larger than 1!
		
		// perform refresh computations
		
		if (++refresh_tally>=refreshes_per_new_peak_bubble) refresh_tally=0;
		refreshes_left[refresh_tally]=left;
		refreshes_right[refresh_tally]=right;
		if (left>=refreshes_left[current_max_refresh_left])
	
	// remember, this might simply be because left stepped on the old champion!
	// ...search for new champion
	
			{
			int y;
			int maxpos=0;
			for (y=0;y<refreshes_per_new_peak_bubble;y++)
				if (refreshes_left[y]>refreshes_left[maxpos]) maxpos=y;
			current_max_refresh_left=maxpos;
			}
		if (right>=refreshes_right[current_max_refresh_right])
			// same as above!
			// ...search for new champion
			{
			int y;
			int maxpos=0;
			for (y=0;y<refreshes_per_new_peak_bubble;y++)
				if (refreshes_right[y]>refreshes_right[maxpos]) maxpos=y;
			current_max_refresh_right=maxpos;
			}
		
		// Draw away...
			
		if (VOLUMEMETER_draw_wide(&valueRect))		// draw wide
			{
				
			if (stereo)
				{
				
				// note that right and left are flipped,
				// so that when displaying wide, left is on the top.
				
				drawRectRight=valueRect;
				drawRectRight.size.height*=1-VOLUMEMETER_RIGHT_BEGIN;
				drawRectRight.origin.y+=valueRect.size.height*
						VOLUMEMETER_RIGHT_BEGIN;
				drawRectRight.size.width*=left;
			
				drawRectLeft=valueRect;
				drawRectLeft.size.height*=VOLUMEMETER_LEFT_END;
				drawRectLeft.size.width*=right;
				}
			else
				{
				drawRectRight=valueRect;
				drawRectRight.size.width*=(right+left)/2.0;
				}
			}
		else										// draw tall
			{
			
			if (stereo)
				{
				drawRectRight=valueRect;
				drawRectRight.size.width*=1-VOLUMEMETER_RIGHT_BEGIN;
				drawRectRight.origin.x+=valueRect.size.width*
						VOLUMEMETER_RIGHT_BEGIN;
				drawRectRight.size.height*=right;
			
				drawRectLeft=valueRect;
				drawRectLeft.size.width*=VOLUMEMETER_LEFT_END;
				drawRectLeft.size.height*=left;
				}
			else
				{
				drawRectRight=valueRect;
				drawRectRight.size.height*=(right+left)/2.0;
				}
			}
		if (left+right>0.0)			
		
		// I go through the computation because peak bubbles need it
		
			{
			PSsetgray(value_gray);
			NXRectFill(&drawRectRight);
			if (stereo) NXRectFill(&drawRectLeft);
			}
		// Draw Peak Bubbles			

		if (peak_bubble_displayed)
			{
			NXRect rightRect=drawRectRight;
			NXRect leftRect=drawRectLeft;
			float max_left=refreshes_left[current_max_refresh_left];
			float max_right=refreshes_right[current_max_refresh_right];
			
			if (max_left+max_right>0.0)
				{
			
				if (VOLUMEMETER_draw_wide(&valueRect))		// draw wide
					{
					rightRect.size.width=0.1;			// ...makes it a line
					leftRect.size.width=0.1;
					
					if (stereo)
						{
						rightRect.origin.x=floor(drawRectRight.origin.x+
							valueRect.size.width*max_left);
						leftRect.origin.x=floor(drawRectLeft.origin.x+
							valueRect.size.width*max_right);
						}
					else
						{
						rightRect.origin.x=floor(drawRectRight.origin.x+
							valueRect.size.width*
							(max_right+max_left)/2.0);
						}
					}	
				else										// draw tall
					{
					rightRect.size.height=0.1;				// makes it a line
					leftRect.size.height=0.1;
					
					if (stereo)
						{
						rightRect.origin.y=floor(drawRectRight.origin.y+
							valueRect.size.height*max_right);
						leftRect.origin.y=floor(drawRectLeft.origin.y+
							valueRect.size.height*max_left);
						}
					else
						{
						rightRect.origin.y=floor(drawRectRight.origin.y+
							valueRect.size.height*
							(max_right+max_left)/2.0);
						}
					}
				PSsetgray(bubble_gray);
				NXRectFill(&rightRect);
				if (stereo) NXRectFill(&leftRect);
				}
			}
		}
	NXPing();
	if ([delegate respondsTo:@selector(meterDidUpdate:)])
		[delegate meterDidUpdate:self];
	// else...
	return self;
	}


- setMono
	{
	stereo=NO;
	//[self display];
	return self;
	}
	
- setStereo
	{
	stereo=YES;
	//[self display];
	return self;
	}

- setBackgroundGray:(float) this_value
	{
	if (this_value>=0&&this_value<=1) background_gray=this_value;
	[self display];
	return self;
	}

- setValueGray:(float) this_value
	{
	if (this_value>=0&&this_value<=1) value_gray=this_value;
	[self display];
	return self;
	}

- setBubbleGray: (float) this_value
	{
	if (this_value>=0&&this_value<=1) bubble_gray=this_value;
	[self display];
	return self;
	}

- (float) backgroundGray
	{
	return background_gray;
	}

- (float) valueGray
	{
	return value_gray;
	}

- (float) bubbleGray
	{
	return bubble_gray;
	}

- setBezeled:(BOOL) yes_or_no
	{
	bezeled=yes_or_no;
	[self display];
	return self;
	}

- setPeakBubbleDisplayed:(BOOL) yes_or_no
	{
	peak_bubble_displayed=yes_or_no;
	[self display];
	return self;
	}

- setToInput
	{
	input=YES;
		// try to allocate once more...
	[self reclaim];
		// ...then test
	if (input_device==NULL) return NULL;
	return self;
	}

- setToOutput
	{
	input=NO;
		// try to allocate once more...
	[self reclaim];
		// ...then test
	if (output_device==NULL) return NULL;
	return self;
	}

- setRefresh:(float) number_seconds
	{
	if (number_seconds>0)
		{
		refresh=number_seconds;
		if (teNum) 
			{
			DPSRemoveTimedEntry(teNum);
			teNum=DPSAddTimedEntry(refresh, 
				(DPSTimedEntryProc) VOLUMEMETER_update_meter,
				(void*) self, (int) NX_RUNMODALTHRESHOLD);
			}
		}
	return self;
	}
	
- setRefreshesPerNewPeakBubble:(int) number_refreshes
	{
	if (number_refreshes<=VOLUMEMETER_MAX_REFRESHES&&number_refreshes>0)
		{
		int x;
		refreshes_per_new_peak_bubble=number_refreshes;
		for (x=0;x<number_refreshes;x++) 
			{refreshes_left[x]=0.0;refreshes_right[x]=0.0;}
		current_max_refresh_left=0;
		current_max_refresh_right=0;
		refresh_tally=0;
		}
	return self;
	}

- reclaim
	{
	// try to grab devices
	
	if (input_device==NULL) input_device=[[NXSoundIn alloc] init];
	if (output_device==NULL) output_device=[[NXSoundOut alloc] init];
	
	// reset devices
	
	[input_device setDetectPeaks:YES];
	[output_device setDetectPeaks:YES];
	
	// don't display here!  That would create a loop.
	return self;
	}

- run
	{
	running=YES;
	//printf ("Run\n");
	if (teNum) DPSRemoveTimedEntry(teNum);
	teNum=DPSAddTimedEntry(refresh, 
			(DPSTimedEntryProc) VOLUMEMETER_update_meter,
			(void*) self, (int) NX_RUNMODALTHRESHOLD);
	[self display];
	return self;
	}

- stop
	{
	running=NO;
	//printf ("Stop\n");
	if (teNum) DPSRemoveTimedEntry(teNum);
	teNum=0;
	[self display];
	return self;
	}
	
- read:(NXTypedStream*) stream
	{
	[super read:stream];
	/*NXReadTypes(stream,"cccccfff",&input,&running,&bezeled,
		&peak_bubble_displayed,&stereo,
		&background_gray,&value_gray,&bubble_gray);*/
	// Commented out to provide new read format:
	NXReadTypes(stream,"ccccffffi",&input,&bezeled,
		&peak_bubble_displayed,&stereo,
		&background_gray,&value_gray,&bubble_gray,&refresh,
		&refreshes_per_new_peak_bubble);
	return self;
	}

- write:(NXTypedStream*) stream
	{
	[super write:stream];
	/*NXWriteTypes(stream,"cccccfff",&input,&running,&bezeled,
		&peak_bubble_displayed,&stereo,
		&background_gray,&value_gray,&bubble_gray);*/
	// Commented out to provide new write format:
	NXWriteTypes(stream,"ccccffffi",&input,&bezeled,
		&peak_bubble_displayed,&stereo,
		&background_gray,&value_gray,&bubble_gray,&refresh,
		&refreshes_per_new_peak_bubble);
	return self;
	}

- free
	{
	if (teNum) DPSRemoveTimedEntry(teNum);
	teNum=0;
	if (input_device!=NULL) [input_device free];
	if (output_device!=NULL) [output_device free];
	return [super free];
	}

- awake
	{
	int x;
	
	[super awake];
	
	refresh=VOLUMEMETER_TIMED_ENTRY_SPEED;
	refreshes_per_new_peak_bubble=VOLUMEMETER_STD_REFRESHES;
	refresh_tally=0;
	for (x=0;x<VOLUMEMETER_MAX_REFRESHES;x++) 
		{refreshes_left[x]=0.0;refreshes_right[x]=0.0;}
	current_max_refresh_left=0;
	current_max_refresh_right=0;
	
	if (input_device==NULL) input_device= [[NXSoundIn alloc] init];
	if (output_device==NULL) output_device=[[NXSoundOut alloc] init];
	[input_device setDetectPeaks:YES];
	[output_device setDetectPeaks:YES];			// for symmetry;

	//if (running) [self run];
	return self;
	}

- setMono:sender
	{
	return [self setMono];
	}

- setStereo:sender
	{
	return [self setStereo];
	}

- setToInput:sender
	{
	return [self setToInput];
	}

- setToOutput:sender
	{
	return [self setToOutput];
	}

- run:sender
	{
	return [self run];
	}

- stop:sender
	{
	return [self stop];
	}

- windowDidBecomeKey:sender
	{
	id temp=self;
	//if ([delegate respondsTo:@selector(windowDidBecomeKey:)])
	//	temp=[delegate windowDidBecomeKey:sender];
	if (temp!=NULL) [self reclaim];
	if (temp!=NULL) [self run];
	return self;
	}
	
	
- windowDidBecomeMain:sender
	{
	id temp=self;
	//if ([delegate respondsTo:@selector(windowDidBecomeMain:)])
	//	temp=[delegate windowDidBecomeMain:sender];
	if (temp!=NULL) [self reclaim];
	if (temp!=NULL) [self run];
	return self;
	}
	
	

- windowDidDeminiaturize:sender
	{
	id temp=self;
	//if ([delegate respondsTo:@selector(windowDidDeminiaturize:)])
	//	temp=[delegate windowDidDeminiaturize:sender];
	if (temp!=NULL) [self reclaim];
	if (temp!=NULL) [self run];
	return self;
	}
	
	

- windowDidMiniaturize:sender
	{
	id temp=self;
	//if ([delegate respondsTo:@selector(windowDidMiniaturize:)])
	//	temp=[delegate windowDidMiniaturize:sender];
	if (temp!=NULL) [self stop];
	return self;
	}
	
	
- windowWillClose:sender
	{
	id temp=self;
	//if ([delegate respondsTo:@selector(windowWillClose:)])
	//	temp=[delegate windowWillClose:sender];
	if (temp!=NULL) [self stop];
	return self;
	}
	
- setSound:this_sound
	{
	sound=this_sound;
	[self display];
	return self;
	}

- sound
	{
	return sound;
	}
	
- setDelegate:this_delegate
	{
	delegate=this_delegate;
	return self;
	}
	
- delegate
	{
	return delegate;
	}
	

- (BOOL) isBezeled:sender
	{
	return bezeled;
	}
	
- (BOOL) peakBubbleDisplayed:sender
	{
	return peak_bubble_displayed;
	}
	

- (BOOL) isInput:sender
	{
	return input;
	}
	

- (BOOL) isStereo:sender
	{
	return stereo;
	}
	

- (float) refresh:sender
	{
	return refresh;
	}
	

- (int) refreshesPerPeakBubble:sender
	{
	return refreshes_per_new_peak_bubble;
	}
	
	
// The following are delegate methods, just listed here to stop warnings
- meterWillUpdateOnOwn:sender {return NULL;}
- meterWillUpdate:sender {return NULL;}	
- meterDidUpdate:sender	{return NULL;}	
	
- (const char*) getInspectorClassName
	{
	return "MiscVolumeMeterInspector";
	}
	
@end
